package log

import (
	"fmt"
	"os"
	"path/filepath"
	"sync"
	"time"
)

/*
本文件实现了日志文件写入器（FileWriter），负责将日志内容写入文件，并支持日志轮转（Log Rotation）功能。
日志轮转是指当当前日志文件达到指定条件（如大小上限、存在时间上限）时，自动将当前文件重命名为备份文件，
并创建新文件继续写入，同时清理过期或过多的备份文件，避免日志文件过大或占用过多磁盘空间。
*/

// FileWriter 日志文件写入器，负责日志的文件写入、自动轮转和备份清理
type FileWriter struct {
	mu          sync.Mutex    // 互斥锁，保证多线程环境下文件操作的并发安全（避免同时写入或轮转导致的文件错乱）
	file        *os.File      // 当前打开的日志文件句柄，用于实际写入日志
	path        string        // 日志文件路径（如"logs/app.log"），当前日志始终写入该路径
	maxSize     int64         // 单个日志文件的最大大小（字节），超过此值触发轮转
	maxAge      time.Duration // 日志文件的最大保存时间，超过此时长触发轮转
	maxBackups  int           // 最多保留的备份文件数量，超过此数量时删除最旧的备份
	currentSize int64         // 当前日志文件的大小（字节），用于判断是否需要轮转
}

// NewFileWriter 创建一个日志文件写入器实例，初始化日志文件和轮转参数
// 参数：
//
//	path: 日志文件路径（如"logs/app.log"）
//	maxSize: 单个日志文件的最大大小（单位：MB）
//	maxAge: 日志文件的最大保存时间（如24*time.Hour表示1天）
//	maxBackups: 最多保留的备份文件数量
//
// 返回值：
//
//	*FileWriter: 初始化后的文件写入器实例
//	error: 创建失败时返回错误（如目录创建失败、文件打开失败）
func NewFileWriter(path string, maxSize int64, maxAge time.Duration, maxBackups int) (*FileWriter, error) {
	// 确保日志文件所在的目录存在（如"logs/"目录，若不存在则创建）
	dir := filepath.Dir(path) // 获取路径中的目录部分（如"logs/app.log"的目录是"logs"）
	// MkdirAll递归创建目录，0755表示目录权限（所有者可读写执行，组和其他用户可读执行）
	if err := os.MkdirAll(dir, 0755); err != nil {
		return nil, fmt.Errorf("failed to create log directory: %v", err)
	}

	// 初始化FileWriter实例，注意将maxSize从MB转换为字节（1MB=1024*1024字节）
	writer := &FileWriter{
		path:       path,
		maxSize:    int64(maxSize * 1024 * 1024), // 转换为字节
		maxAge:     maxAge,
		maxBackups: maxBackups,
	}

	// 打开日志文件（首次创建或复用已有文件）
	if err := writer.openFile(); err != nil {
		return nil, err
	}

	return writer, nil
}

// Write 实现io.Writer接口，将日志数据写入文件，自动判断是否需要轮转
// 参数：
//
//	p: 要写入的日志数据（字节切片）
//
// 返回值：
//
//	n: 实际写入的字节数
//	err: 写入失败时返回错误
func (w *FileWriter) Write(p []byte) (n int, err error) {
	w.mu.Lock()         // 加锁，确保写入和轮转操作的原子性（避免并发写入导致数据错乱）
	defer w.mu.Unlock() // 函数退出时自动解锁

	// 检查是否需要触发日志轮转（当前文件大小+新数据大小是否超限，或文件是否过期）
	if w.needsRotation(int64(len(p))) {
		if err := w.rotate(); err != nil { // 执行轮转
			return 0, err
		}
	}

	// 将数据写入当前日志文件
	n, err = w.file.Write(p)
	if err != nil {
		return n, err // 写入失败，返回已写入字节数和错误
	}

	w.currentSize += int64(n) // 更新当前文件大小
	return n, nil
}

// Close 关闭日志文件，释放文件句柄
// 返回值：
//
//	error: 关闭失败时返回错误（如文件已关闭）
func (w *FileWriter) Close() error {
	w.mu.Lock() // 加锁，确保关闭操作的安全性
	defer w.mu.Unlock()

	if w.file != nil {
		return w.file.Close() // 关闭文件句柄，释放资源
	}
	return nil // 文件已关闭时直接返回nil
}

// openFile 打开或创建日志文件，初始化当前文件大小
// 返回值：
//
//	error: 打开/关闭文件失败时返回错误
func (w *FileWriter) openFile() error {
	// 若已有打开的文件，先关闭（避免句柄泄露）
	if w.file != nil {
		if err := w.file.Close(); err != nil {
			return err
		}
	}

	// 打开日志文件：
	// - os.O_WRONLY: 只写模式
	// - os.O_APPEND: 追加模式（新数据写在文件末尾）
	// - os.O_CREATE: 若文件不存在则创建
	// 0644: 文件权限（所有者可读写，组和其他用户可读）
	file, err := os.OpenFile(w.path, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
	if err != nil {
		return fmt.Errorf("failed to open log filesource: %v", err)
	}

	// 获取文件信息（用于获取当前文件大小）
	info, err := file.Stat()
	if err != nil {
		file.Close() // 失败时关闭已打开的文件
		return fmt.Errorf("failed to get filesource info: %v", err)
	}

	// 更新文件句柄和当前大小
	w.file = file
	w.currentSize = info.Size()
	return nil
}

// needsRotation 判断是否需要触发日志轮转
// 参数：
//
//	dataSize: 即将写入的日志数据大小（字节）
//
// 返回值：
//
//	bool: true表示需要轮转，false表示不需要
func (w *FileWriter) needsRotation(dataSize int64) bool {
	// 条件1：当前文件大小 + 新数据大小 > 最大允许大小（触发轮转）
	if w.currentSize+dataSize > w.maxSize {
		return true
	}

	// 条件2：若设置了最大保存时间，且文件修改时间超过该时间（触发轮转）
	if w.maxAge > 0 {
		info, err := w.file.Stat() // 获取文件的修改时间
		if err == nil {            // 成功获取文件信息
			// 计算当前时间与文件最后修改时间的差值，若超过maxAge则需要轮转
			if time.Since(info.ModTime()) > w.maxAge {
				return true
			}
		}
	}

	return false // 不满足任何条件，不需要轮转
}

// rotate 执行日志轮转：将当前文件重命名为备份，创建新文件，清理旧备份
// 返回值：
//
//	error: 轮转过程中出现错误时返回
func (w *FileWriter) rotate() error {
	// 关闭当前日志文件（避免重命名时文件被占用）
	if w.file != nil {
		if err := w.file.Close(); err != nil {
			return err
		}
	}

	// 生成备份文件路径：在原路径后添加时间戳（如"logs/app.log" → "logs/app.log.20240520153045"）
	// 时间戳格式"20060102150405"对应年月日时分秒，确保唯一性
	timestamp := time.Now().Format("20060102150405")
	backupPath := fmt.Sprintf("%s.%s", w.path, timestamp)

	// 将当前日志文件重命名为备份文件
	if err := os.Rename(w.path, backupPath); err != nil {
		return fmt.Errorf("failed to rename log filesource: %v", err)
	}

	// 打开新的日志文件（路径仍为w.path），后续日志将写入新文件
	if err := w.openFile(); err != nil {
		return err
	}

	// 清理过期或过多的备份文件
	if err := w.cleanupBackups(); err != nil {
		return err
	}

	return nil
}

// cleanupBackups 清理超过maxBackups数量的旧备份文件，保留最新的指定数量备份
// 返回值：
//
//	error: 清理过程中出现错误时返回
func (w *FileWriter) cleanupBackups() error {
	if w.maxBackups <= 0 {
		return fmt.Errorf("maxBackups must be greater than 0") // 不允许maxBackups为0或负数
	}

	// 查找所有符合格式的备份文件（原路径+".*"，如"logs/app.log.*"）
	pattern := fmt.Sprintf("%s.*", w.path)
	matches, err := filepath.Glob(pattern) // Glob用于匹配符合模式的文件路径
	if err != nil {
		return fmt.Errorf("failed to find backup files: %v", err)
	}

	// 若备份文件数量超过maxBackups，需要删除多余的（保留最新的maxBackups个）
	if len(matches) > w.maxBackups {
		// 收集备份文件的信息（主要是修改时间，用于排序）
		files := make([]os.FileInfo, 0, len(matches))
		for _, match := range matches {
			info, err := os.Stat(match) // 获取文件信息（包含修改时间）
			if err == nil {             // 忽略获取失败的文件
				files = append(files, info)
			}
		}

		// 按修改时间排序（旧→新），此处代码简化，实际需实现排序逻辑
		// 例如：sort.Slice(files, func(i, j int) bool { return files[i].ModTime().Before(files[j].ModTime()) })

		// 删除最旧的（len(files)-maxBackups）个文件
		// 例如：总共有5个备份，maxBackups=3，则需要删除最旧的2个
		for i := 0; i < len(files)-w.maxBackups; i++ {
			os.Remove(matches[i]) // 删除文件（此处matches与files顺序需一致，依赖排序）
		}
	}

	return nil
}
